package semantics;

import ic.LanguageException;
import ic.ast.BinaryOp;
import ic.ast.ClassType;
import ic.ast.DeclClass;
import ic.ast.DeclField;
import ic.ast.DeclLibraryMethod;
import ic.ast.DeclMethod;
import ic.ast.DeclStaticMethod;
import ic.ast.DeclVirtualMethod;
import ic.ast.HasName;
import ic.ast.Length;
import ic.ast.Literal;
import ic.ast.LocalVariable;
import ic.ast.NewArray;
import ic.ast.NewInstance;
import ic.ast.Node;
import ic.ast.Parameter;
import ic.ast.PrimitiveType;
import ic.ast.Program;
import ic.ast.RefArrayElement;
import ic.ast.RefField;
import ic.ast.RefVariable;
import ic.ast.Statement;
import ic.ast.StaticCall;
import ic.ast.StmtAssignment;
import ic.ast.StmtBlock;
import ic.ast.StmtBreak;
import ic.ast.StmtCall;
import ic.ast.StmtContinue;
import ic.ast.StmtIf;
import ic.ast.StmtReturn;
import ic.ast.StmtWhile;
import ic.ast.This;
import ic.ast.UnaryOp;
import ic.ast.VirtualCall;
import ic.ast.Visitor;

public class ScopeFiller implements Visitor {
	Scope cs; // curr scope
	Scope program_scope;
	int counterLoopDepth = 0;
	private boolean flgStatic = false;

	Node lib_ast = null;

	public ScopeFiller(Node lib_ast) {
		this.lib_ast = lib_ast;
	}

	@Override
	public Object visit(Program program) {
		program_scope = cs = program.setScope(Scope.makeNewScopeTree(program));

		if (lib_ast != null) {
			lib_ast.accept(this);
		}

		for (DeclClass cd : program.getClasses()) {
			cd.accept(this);
		}
		return program_scope;
	}

	@Override
	public Object visit(DeclClass icClass) {
		Scope static_scope;

		if (icClass.hasSuperClass()) {
			Node nd_superclass = program_scope.getFlat(icClass
					.getSuperClassName());
			if (nd_superclass == null)
				throw new LanguageException(icClass.getLine(),
						"Class " + icClass.getName() + " cannot extend " + icClass.getSuperClassName()
								+ ", since it's not yet defined");
			if (!(nd_superclass instanceof DeclClass))
				throw new LanguageException(icClass.getLine(),
						"cannot inherit from "
								+ ((HasName) nd_superclass)
										.getFunctionalJobDescription());

			cs = program_scope.addAndExtend(icClass,
					((DeclClass) nd_superclass).getInstanceScope());
			static_scope = program_scope.makeChildScope(icClass,
					((DeclClass) nd_superclass).getStaticScope());

		} else {
			static_scope = program_scope.makeChildScope(icClass, program_scope);
			cs = program_scope.addAndExtend(icClass);
		}
		icClass.setInstanceScope(cs);

		icClass.setStaticScope(static_scope);

		if (icClass.getFields() != null) {
			for (DeclField f : icClass.getFields()) {
				f.accept(this);
			}
		}

		if (icClass.getMethods() != null) {
			for (DeclMethod dm : icClass.getMethods()) {
				if (dm instanceof DeclVirtualMethod) {
					Scope old_cs = cs;
					dm.accept(this);
					cs = old_cs;
				} else {
					Scope old_cs = cs;
					cs = static_scope;
					flgStatic = true;
					dm.accept(this);
					flgStatic = false;
					cs = old_cs;
				}
			}
		}

		cs = program_scope;

		return null;
	}

	@Override
	public Object visit(DeclField field) {
		cs.addToScope(field);
		return null;
	}

	@Override
	public Object visit(DeclVirtualMethod method) {
		cs = cs.addAndExtend(method);

		for (Parameter parameter : method.getFormals()) {
			parameter.accept(this);
		}

		for (Statement st : method.getStatements()) {
			st.accept(this);
		}

		cs = cs.getParent();

		return null;
	}

	@Override
	public Object visit(DeclStaticMethod method) {
		cs = cs.addAndExtend(method);

		for (Parameter parameter : method.getFormals()) {
			parameter.accept(this);
		}

		for (Statement st : method.getStatements()) {
			st.accept(this);
		}

		cs = cs.getParent();

		return null;
	}

	@Override
	public Object visit(DeclLibraryMethod method) {
		cs = cs.addAndExtend(method);

		if (method.getFormals() != null) {
			for (Parameter parameter : method.getFormals()) {
				parameter.accept(this);
			}
		}

		cs = cs.getParent();

		return null;
	}

	@Override
	public Object visit(Parameter formal) {
		cs.addToScope(formal);
		return null;
	}

	@Override
	public Object visit(PrimitiveType type) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(ClassType type) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(StmtAssignment assignment) {
		cs.addToScope(assignment);
		assignment.getVariable().accept(this);
		return null;
	}

	@Override
	public Object visit(StmtCall callStatement) {
		cs.addToScope(callStatement);
		callStatement.getCall().accept(this);
		return null;
	}

	@Override
	public Object visit(StmtReturn returnStatement) {
		cs.addToScope(returnStatement);
		return null;
	}

	@Override
	public Object visit(StmtIf ifStatement) {
		cs.addToScope(ifStatement);
		ifStatement.getOperation().accept(this);
		if (ifStatement.hasElse())
			ifStatement.getElseOperation().accept(this);
		return null;
	}

	@Override
	public Object visit(StmtWhile w) {
		cs.addToScope(w);

		++counterLoopDepth;

		w.getOperation().accept(this);

		--counterLoopDepth;
		return null;
	}

	@Override
	public Object visit(StmtBreak s) {
		cs.addToScope(s);
		if (counterLoopDepth == 0)
			throw new LanguageException(s.getLine(),
					"Use of 'break' statement outside of loop not allowed");
		return null;
	}

	@Override
	public Object visit(StmtContinue s) {
		cs.addToScope(s);
		if (counterLoopDepth == 0)
			throw new LanguageException(s.getLine(),
					"Use of 'continue' statement outside of loop not allowed");
		return null;
	}

	@Override
	public Object visit(StmtBlock b) {
		cs = cs.addAndExtend(b);

		for (Statement s : b.getStatements()) {
			s.accept(this);
		}

		cs = cs.getParent();

		return null;
	}

	@Override
	public Object visit(LocalVariable localVariable) {
		cs.addToScope(localVariable);
		return null;
	}

	@Override
	public Object visit(RefVariable r) {
		cs.addToScope(r);
		return null;
	}

	@Override
	public Object visit(RefField r) {
		cs.addToScope(r);
		return null;
	}

	@Override
	public Object visit(RefArrayElement r) {
		cs.addToScope(r);
		return null;
	}

	@Override
	public Object visit(StaticCall r) {
		cs.addToScope(r);
		return null;
	}

	@Override
	public Object visit(VirtualCall r) {
		cs.addToScope(r);
		return null;
	}

	@Override
	public Object visit(This a) {
		if (flgStatic)
			throw new LanguageException(a.getLine(),
					"this illegal in static context");
		cs.addToScope(a);
		return null;
	}

	@Override
	public Object visit(NewInstance a) {
		cs.addToScope(a);
		return null;
	}

	@Override
	public Object visit(NewArray a) {
		cs.addToScope(a);
		return null;
	}

	@Override
	public Object visit(Length a) {
		cs.addToScope(a);
		return null;
	}

	@Override
	public Object visit(Literal a) {
		cs.addToScope(a);
		return null;
	}

	@Override
	public Object visit(UnaryOp a) {
		cs.addToScope(a);
		return null;
	}

	@Override
	public Object visit(BinaryOp a) {
		cs.addToScope(a);
		return null;
	}

}
